#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
#include "connexion.h"
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

// Le port FTP pour les données est le port 20, et les commandes sur le port 21

#define TAILLE_BUFF 1000

typedef struct 
{
	int s;
	SSL* ssl;
	SSL_CTX* ctx;
	
	int s_tranfert;
} connection;

int recoisMessageServ(int s, char* buff)
{
	int i, res;
	int fini = 0;
	int taille = 0;
	char fin = 'a'; // valeur pour commencer la boucle
	while (fini == 0)
	{ 
		//On récupère dans buff les données que le serveur a à envoyer.
		res = recv(s, (char*)buff+taille, (TAILLE_BUFF-taille)*sizeof(char), 0);
		taille += res;
		//Gestion des erreurs
		if (res < 0) 
		{
			perror("ERR: erreur dans le recv");
			break;
		}

		//Le serveur n'a plus rien à envoyer, on sort de la boucle while
		if (res == 0)
		{
			perror("ERR: connexion coupé");
			break;
		}
		
		//Vérification que le serveur n'a plus rien à envoyer : Il envoie "\r\n" quand il a fini"
		for (i = 0; i < res; i++)
		{
			if (buff[i] == '\n')
			{
				if (fin == '\r')
				{
					buff[i+1] = '\0';
					fini = 1;
				}
			}
			fin = buff[i];
		}
		// on garde en memoire le dernier caractere envoyé (si c'est \r)
		fin = buff[res -1];
	}
  return res;
}

int recoisMessage(int s)
{
	char buff[TAILLE_BUFF];
	int res = recoisMessageServ(s, buff);
	if (buff[0] == '5')
			fprintf(stderr, "ERR: ");
		else if (buff[0] == '4')
			fprintf(stderr, "FAIL: ");
		else
			fprintf(stderr, "OK: ");
	fprintf(stderr, "%s", buff);
	return res;
}

int envoieMessage(int s, char* buff)
{
	int i, res, taille;
	
	if(!strcmp(buff, "fin\n") || !strcmp(buff, "FIN\n"))
	{
		return 0;  // 0 pour indiquer qu'il faut s'arreter
	}
	
	// fgets rajoute le retour à la ligne (en appuyant sur "entrée")
	// on le supprime donc
	buff[strlen(buff) -1] = '\0';
	// on rajoute le \r\n a la fin du message à envoyer
	strcat(buff, "\r\n");
	
	taille = strlen(buff);
	i = 0;
	while (i < taille) 
	{ // attention, il ne faut pas envoyer le \0
		res = send(s, buff+i, taille-i, MSG_NOSIGNAL);
		if(res<=0)
		{
			fprintf(stderr, "ERR: write %s car %s\n", buff, strerror(errno));
			return -1;
		}
		i += res;
	}
	return i;  // sinon on renvoie la taille du message envoyé
}

int connectSocket(char* host, char* port)
{
// 	const char* host = argv[1];
// 	const char* host = argv[2];
	// structure pour faire la demande
	struct addrinfo hints;
	// structure pour stocker et lire les résultats
	struct addrinfo *result, *rp;
	int s;
	// socket  (s)
	s=-1;
	// variables pour tester si les fonctions donnent un résultat ou une erreur
	int res;
	int bon;
	// Des variable pour contenir des adresses de machine et des numero de port afin de les afficher
	char hname[NI_MAXHOST], sname[NI_MAXSERV];
	// Des variable pour stocker des infos sur le client lui même.
	struct sockaddr_storage my_addr;
	socklen_t my_addr_len = sizeof(my_addr);


	// on remplit la structure hints de demande d'adresses
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;    /* IPv4 ou IPv6 */
	hints.ai_socktype = SOCK_STREAM; /* socket flux connectée */
	hints.ai_flags = 0;
	hints.ai_protocol = 0;          /* Any protocol */
	hints.ai_addrlen = 0;
	hints.ai_addr = NULL;
	hints.ai_canonname = NULL;
	hints.ai_next = NULL;

	res = getaddrinfo(host, port, &hints, &result);
	if (res != 0)
	{ // c'est une erreur
		fprintf(stderr, "ERR: getaddrinfo: %s\n", gai_strerror(res));
		exit(1);
	}

	// si res = 0 le véritable résultat de la fontion est l'argument result
	// qui contient une liste d'adresses correspondant à la demande on va les
	// tester jusqu'à trouver une qui convienne.
	rp = result;
	bon = 0;
	while (rp != NULL) {
		// on parcourt la liste pour en trouver une qui convienne

		// on récupère des informations affichables
		res = getnameinfo(rp->ai_addr, rp->ai_addrlen,
					hname, NI_MAXHOST,
					sname, NI_MAXSERV,
					NI_NUMERICSERV|NI_NUMERICHOST);
		if (res != 0) {
			fprintf(stderr, "ERR: getnameinfo: %s\n", gai_strerror(res));
			exit (1);
		}
		//fprintf (stderr, "OK: On tente l'adresse %s sur le port %s .....",
		//	hname, sname);

		// on essaie
		s = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);
		// si le résultat est -1 cela n'a pas fonctionné on recommence avec la prochaine
		if (s == -1) {
			perror("ERR: Création de la socket");
			rp = rp->ai_next;
			continue;
		}

		// si la socket a été obtenue, on essaie de se connecter
		res = connect(s, rp->ai_addr, rp->ai_addrlen);
		if (res == 0 ) {// cela a fonctionné on est connecté
			bon = 1;
			fprintf (stderr, "OK: connexion prete\n");
			break;
		}
		else { // sinon le bind a été impossible, il faut fermer la socket
			perror("ERR: Impossible de se connecter");
			close (s);
		}

		rp = rp->ai_next;
	}

	if (bon == 0) { // Cela n'a jamais fonctionné
		fprintf(stderr, "ERR: Aucune connexion possible\n");
		exit(1);
	}

	// affichage des infos sur la connexion
	// on affiche des infos sur le serveur
	res = getnameinfo(rp->ai_addr, rp->ai_addrlen,
				hname, NI_MAXHOST,
				sname, NI_MAXSERV,
				NI_NUMERICSERV|NI_NUMERICHOST);
	if (res != 0) {
		fprintf(stderr, "ERR: getnameinfo: %s\n", gai_strerror(res));
		exit (1);
	}
	printf ("OK: Le serveur contacté est %s sur le port %s\n", hname, sname);
	// on affiche des info su.r le client
	res = getsockname(s, (struct sockaddr *) &my_addr, &my_addr_len);
	if (res != 0) {
		fprintf(stderr, "ERR: getnameinfo: %s\n", gai_strerror(res));
		exit (1);
	}
	res = getnameinfo((struct sockaddr *) &my_addr, my_addr_len,
				hname, NI_MAXHOST,
				sname, NI_MAXSERV,
				NI_NUMERICSERV|NI_NUMERICHOST);
	if (res != 0) {
		fprintf(stderr, "ERR: getnameinfo: %s\n", strerror(errno));
		exit (1);
	}
	printf ("OK: Je suis %s et j'utilise le port %s\n", hname, sname);

	freeaddrinfo(result);           /* No longer needed  */
	
	return s;
}

void fermetureSocket(int s)
{
	char quit[10];
	strcpy(quit, "QUIT\n");
	envoieMessage(s, quit);
	recoisMessage(s);
	if (close(s) < 0) {
		perror("ERR: Problème à la fermeture de la socket");
	}

	fprintf(stdout, "OK: bye\n");
}

int dialogue(int s, int argc, char** argv)
{
	int res;
	char buff[TAILLE_BUFF];
	// Il y a un nom d'utilisateur
	if (argc > 3)
	{
		char user[TAILLE_BUFF];
		strcpy(user, "USER ");
		strcat(user, argv[3]);
		strcat(user, "\n");

		printf("%s", user);
		envoieMessage(s,user);
	}

	// Il y a un mot de passe
	if (argc > 4)
	{
		recoisMessage(s);
		char pass[TAILLE_BUFF];
		strcpy(pass, "PASS ");
		strcat(pass, argv[4]);
		strcat(pass, "\n");

		printf("%s", pass);
		envoieMessage(s, pass);
	}

	do
	{
		recoisMessage(s);
		printf(">");
		fgets(buff, TAILLE_BUFF, stdin);
		res = envoieMessage(s, buff);
	}
	while(res != 0);
	fermetureSocket(s);
	return 0;
}

int sslRecoisMessageServ (connection* c, char* buff)
{
	int i, res;
	int fini = 0;
	int taille = 0;
	char fin = 'a'; // valeur pour commencer la boucle
	while (fini == 0)
	{ 
		//On récupère dans buff les données que le serveur a à envoyer.
		res = SSL_read(c->ssl, (char*)buff+taille, (TAILLE_BUFF-taille)*sizeof(char));
		taille += res;
		//Gestion des erreurs
		if (res < 0) 
		{
			perror("ERR: erreur dans le recv");
			break;
		}

		//Le serveur n'a plus rien à envoyer, on sort de la boucle while
		if (res == 0)
		{
			perror("ERR: connexion coupé");
			break;
		}
		
		//Vérification que le serveur n'a plus rien à envoyer : Il envoie "\r\n" quand il a fini"
		for (i = 0; i < res; i++)
		{
			if (buff[i] == '\n')
			{
				if (fin == '\r')
				{
					buff[i+1] = '\0';
					fini = 1;
				}
			}
			fin = buff[i];
		}
		// on garde en memoire le dernier caractere envoyé (si c'est \r)
		fin = buff[res -1];
	}
  return res;
}

int sslRecoisMessage(connection* c)
{
	char buff[TAILLE_BUFF];
	int res = sslRecoisMessageServ(c, buff);
	if (buff[0] == '5')
			fprintf(stderr, "ERR: ");
		else if (buff[0] == '4')
			fprintf(stderr, "FAIL: ");
		else
			fprintf(stderr, "OK: ");
	fprintf(stderr, "%s", buff);
	return res;
}

void connectTransfert(connection* c, char* buff)
{
	int i;
// 	char pasv[10];
// 	char buff[TAILLE_BUFF];
// 	strcpy(pasv, "PASV\n");
// 	sslEnvoieMessage(c, pasv);
// 	sslRecoisMessageServ(c, buff);

	char* res;
	res = strtok(buff, "()");
	res = strtok(NULL, "()");	// on a recuperé la chaine X,X,X,X,X,X
// 	printf("%s\n", res);
	char* temp;
	char* hote = (char*)malloc(sizeof(char)*20);
	strcpy(hote, "\0");
	temp = strtok(res, ",");
	for (i = 0; i < 3; i ++)
	{
		hote = strcat(hote, temp);
		hote = strcat(hote, ".");
		temp = strtok(NULL, ",");
	}
	hote = strcat(hote, temp);
// 	printf("%s\n", hote);

	int port_temp = 256 * atoi(strtok(NULL, ","));
	port_temp += atoi(strtok(NULL, ","));
// 	printf("%d\n", port_temp);
	
	char port[10];
	sprintf(port, "%d", port_temp);
// 	printf("%s\n", port);
	
	c->s_tranfert = connectSocket(hote, port);
}

int sslEnvoieMessage(connection* c, char* buff)
{
	int i, res, taille;
	
	if(!strcmp(buff, "fin\n") || !strcmp(buff, "FIN\n"))
	{
		return 0;  // 0 pour indiquer qu'il faut s'arreter
	}
	
	if(!strcmp(buff, "LIST\n") || !strcmp(buff, "list\n") || 
		 strstr(buff, "RETR") || strstr(buff, "retr"))
	{
		char pasv[10];
		char temp[TAILLE_BUFF];
		strcpy(pasv, "PASV\n");
		sslEnvoieMessage(c, pasv);
		sslRecoisMessageServ(c, temp);
		connectTransfert(c, temp);
		puts("yoyo");
		envoieMessage(c->s_tranfert, buff);
		
		puts("yoyo");
		recoisMessage(c->s);
		puts("yoyo");
		return 10;
	}
	else
	{
	// fgets rajoute le retour à la ligne (en appuyant sur "entrée")
	// on le supprime donc
	buff[strlen(buff) -1] = '\0';
	// on rajoute le \r\n a la fin du message à envoyer
	strcat(buff, "\r\n");
	
	taille = strlen(buff);
	i = 0;
	while (i < taille) 
	{ // attention, il ne faut pas envoyer le \0
		res = SSL_write(c->ssl, buff+i, taille-i);
		if(res<=0)
		{
			fprintf(stderr, "ERR: write %s car %s\n", buff, strerror(errno));
			return -1;
		}
		i += res;
	}
	return i;  // sinon on renvoie la taille du message envoyé
	}
}

connection* sslConnectSocket(int s)
{
	connection* c = (connection*)malloc(sizeof(connection));
	c->s = s;
	c->ctx = NULL;
	c->ssl = NULL;
	c->s_tranfert = 0;
	
	const SSL_METHOD* method = SSLv23_client_method();
	

	SSL_library_init();
	SSL_load_error_strings();

	c->ctx = SSL_CTX_new(method);
	if (c->ctx == NULL) {
		ERR_print_errors_fp(stderr);
	}
	SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv2);
	c->ssl = SSL_new(c->ctx);
	if (!c->ssl) {
			ERR_print_errors_fp(stderr);   
			return NULL;
	}

	// ajout de la socket de discution avec le serveur
	if (!SSL_set_fd(c->ssl, c->s)) {
		ERR_print_errors_fp(stderr);
		return NULL;
	}

	// démarage du SSL
	if (SSL_connect((SSL*)c->ssl) < 1) {
		ERR_print_errors_fp(stderr);
		return NULL;
	}
	return c;
}

void sslFermetureSocket(connection* c)
{
	char quit[10];
	strcpy(quit, "QUIT\n");
	sslEnvoieMessage(c, quit);
	sslRecoisMessage(c);
	
	if (close(c->s) < 0) {
		perror("ERR: Problème à la fermeture de la socket");
	}

	fprintf(stdout, "OK: bye\n");
	SSL_shutdown (c->ssl);
	SSL_free (c->ssl);
	SSL_CTX_free(c->ctx);

	free (c);
}

int sslDialogue(int s, int argc, char** argv)
{
	char buff[TAILLE_BUFF];
	int res;
	connection* c;
	c = sslConnectSocket(s);
	
	// on envoie PBSZ 0 puis PROT P
	char param[10];
	strcpy(param, "PBSZ 0\n");
	sslEnvoieMessage(c, param);
	sslRecoisMessage(c);
	strcpy(param, "PROT P\n");
	sslEnvoieMessage(c, param);
	sslRecoisMessage(c);

	// Il y a un nom d'utilisateur
	if (argc > 3)
	{
		char user[TAILLE_BUFF];
		strcpy(user, "USER ");
		strcat(user, argv[3]);
		strcat(user, "\n");

		printf("%s", user);
		sslEnvoieMessage(c, user);
	}

	sslRecoisMessage(c);

	char pass[TAILLE_BUFF];
	strcpy(pass, "PASS ");
	// Il y a un mot de passe
	if (argc > 4)
	{
		strcat(pass, argv[4]);
		strcat(pass, "\n");

		printf("%s", pass);
		sslEnvoieMessage(c, pass);
	}
	else
	{
		printf(">");
		fgets(buff, TAILLE_BUFF, stdin);
		strcat(pass, buff);
		res = sslEnvoieMessage(c, pass);
	}
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	sslRecoisMessage(c);
	do
	{
		sslRecoisMessage(c);
		printf(">");
		fgets(buff, TAILLE_BUFF, stdin);
		res = sslEnvoieMessage(c, buff);
	}
	while(res != 0);
	sslFermetureSocket(c);
	return 0;
}

void initialisation(int argc, char** argv)
{
	char buff[TAILLE_BUFF];
	int s, securise;

	s = connectSocket(argv[1], argv[2]);

	// Message de bienvenue du serveur
	recoisMessageServ(s, buff);
	printf("%s", buff);

	// bout de code pour envoyer AUTH TLS automatiqument au debut (pour le script)

	char auth[10];
	strcpy(auth, "AUTH TLS\n");
	envoieMessage(s, auth);
	recoisMessageServ(s, buff);
	if (buff[0] == '5') {
		fprintf(stderr, "ERR: %s", buff);
		securise = 0;
// 		exit(0);
	}
	else if (buff[0] == '4') {
		fprintf(stderr, "FAIL: %s", buff);
		securise = 0;
// 		exit(0);
	}
	else {
		fprintf(stderr, "OK: %s", buff);
		securise = 1;
	}

	if (securise == 1)
		sslDialogue(s, argc, argv);
	else
		dialogue(s, argc, argv);
}
